package com.hqd.jjvm.instructions.ctrl;

import com.hqd.jjvm.instructions.AbstractInstruction;
import com.hqd.jjvm.instructions.InstructionType;
import com.hqd.jjvm.rtda.JThread;
import com.hqd.jjvm.rtda.StackFrame;
import com.hqd.jjvm.rtda.heap.state.JClassState;
import com.hqd.jjvm.rtda.memory.JMethodArea;
import com.hqd.jjvm.rtda.obj.JObject;

import java.util.HashMap;
import java.util.Map;

public class CtrlInstruction extends AbstractInstruction {

    public CtrlInstruction(JThread jThread) {
        super(jThread);
    }

    @Override
    public void execute(InstructionType instructionType) {
        JThread jThread = getJThread();
        switch (instructionType) {
            case GOTO: {
                int offer = (short) parseAndReadValU2(false).intValue();
                offerIncrement(offer);
                break;
            }
            case JSR:
                break;
            case RET: {
                break;
            }
            case TABLESWITCH:
                break;
            case LOOKUPSWITCH: {
                int key = popOperandStackIntVal();
                //对齐部分
                int defaultOffset = parseAndReadValU4();
                int npairs = parseAndReadValU4();
                Map<Integer, Integer> table = new HashMap<>();
                for (int i = 0; i < npairs; i++) {
                    table.put(parseAndReadValU4(), parseAndReadValU4());
                }
                if (table.get(key) != null) {
                    offerIncrement(table.get(key) - (npairs * 2 + 2) * 4 - 1);
                } else {
                    offerIncrement(defaultOffset - (npairs * 2 + 2) * 4 - 1);
                }
                break;
            }
            case IRETURN: {
                StackFrame csf = invokeReturn(jThread);
                int retVal = csf.popOperandStackIntVal();
                pushOperandStackVal(retVal);
                break;
            }
            case LRETURN: {
                StackFrame csf = invokeReturn(jThread);
                long retVal = csf.popOperandStackLongVal();
                pushOperandStackVal(retVal);
                break;
            }
            case FRETURN: {
                StackFrame csf = invokeReturn(jThread);
                float retVal = csf.popOperandStackFloatVal();
                pushOperandStackVal(retVal);
                break;
            }
            case DRETURN: {
                StackFrame csf = invokeReturn(jThread);
                double retVal = csf.popOperandStackDoubleVal();
                pushOperandStackVal(retVal);
                break;
            }
            case ARETURN: {
                StackFrame csf = invokeReturn(jThread);
                JObject retVal = csf.popOperandStackRefVal();
                pushOperandStackVal(retVal);
                break;
            }
            case RETURN: {
                StackFrame csf = invokeReturn(jThread);
                if (csf.getJMethod().isClinit()) {
                    JMethodArea.getJClass(csf.getJClass().getClassName()).setState(JClassState.FULLY_INITIALIZED);
                }
                break;
            }
        }
    }

    private StackFrame invokeReturn(JThread jThread) {
        StackFrame csf = jThread.getJvmStack().pop();
        if (jThread.getJvmStack().isEmpty()) {
            end();
        } else {
            jThread.setNextPc(jThread.getJvmStack().getTop().getReturnAddress());
        }
        return csf;
    }

    private void stackValIsNonNull() {
        Object o = popOperandStackVal();
        if (o != null) {
            if (o instanceof JObject) {
                if (((JObject) o).getData() != null) {
                    offerIncrement(parseAndReadValU2(false));
                }
            } else {
                offerIncrement(parseAndReadValU2(false));
            }
        }
    }

    private boolean stackValIsNull() {
        Object o = popOperandStackVal();
        if (o == null) {
            offerIncrement(parseAndReadValU2(false));
            return true;
        } else if (o instanceof JObject) {
            if (((JObject) o).getData() == null) {
                offerIncrement(parseAndReadValU2(false));
                return true;
            }
        }
        return false;
    }
}
